home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 8: LINUX Games
/
Linux Cubed Series 8 - LINUX Games.iso
/
games
/
x11
/
rpg
/
crossfir.92
/
crossfir
/
crossfire-0.92.5
/
server
/
monster.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-07-24
|
35KB
|
1,209 lines
/*
* static char *rcsid_monster_c =
* "$Id: monster.c,v 1.27 1996/06/10 06:48:25 master Exp $";
*/
/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 1994 Mark Wedel
Copyright (C) 1992 Frank Tore Johansen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The author can be reached via e-mail to master@rahul.net
*/
#include <global.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#endif
extern spell spells[NROFREALSPELLS];
object *get_enemy(object *npc) {
if ((npc->move_type & HI4) == 16)
if (npc->owner != NULL)
return npc->enemy = npc->owner->enemy;
else npc->enemy = NULL;
if(npc->enemy) {
if(QUERY_FLAG(npc->enemy,FLAG_REMOVED)||QUERY_FLAG(npc->enemy,FLAG_FREED)
||!(RANDOM()%20)||
(npc->enemy->type!=PLAYER&&npc->enemy->type!=GOLEM)||
(npc->enemy->type==PLAYER&&npc->enemy->contr->state)||
npc->enemy->map!=npc->map)
npc->enemy=NULL;
}
return npc->enemy;
}
object *find_enemy(object *npc) {
object *tmp;
if ((npc->move_type & HI4) == PETMOVE)
return get_pet_enemy(npc);
if((tmp=get_enemy(npc))!=NULL)
return tmp;
if(QUERY_FLAG(npc, FLAG_UNAGGRESSIVE))
return NULL;
tmp = get_nearest_player(npc);
if(QUERY_FLAG(npc, FLAG_FRIENDLY)) {
object *op;
if(tmp == NULL)
return NULL;
if((op = get_enemy(tmp))!=NULL)
return op;
return NULL;
}
return tmp;
}
#define MIN_MON_RADIUS 3 /* minimum monster detection radius */
int check_wakeup(object *op, object *enemy) {
int radius = op->stats.Wis>MIN_MON_RADIUS?op->stats.Wis:MIN_MON_RADIUS;
objectlink *ol;
/* blinded monsters can only find nearby objects to attack */
if(QUERY_FLAG(op, FLAG_BLIND) && !QUERY_FLAG(op, FLAG_SEE_INVISIBLE))
radius = MIN_MON_RADIUS;
#ifdef USE_LIGHTING
/* This covers the situation where the monster is in the dark
* and has an enemy. If the enemy has no carried light (or isnt
* glowing!) then the monster has trouble finding the enemy.
* Remember we already checked to see if the monster can see in
* the dark. */
else if(op->map&&op->map->darkness>0&&enemy&&!has_carried_lights(enemy))
{
int dark = radius/(op->map->darkness);
radius = (dark>MIN_MON_RADIUS)?(dark+1):MIN_MON_RADIUS;
}
#endif
else if(!QUERY_FLAG(op,FLAG_SLEEP)) return 1;
for(ol=first_friendly_object;ol!=NULL;ol=ol->next)
if(ol->ob->map==op->map&&
(QUERY_FLAG(ol->ob,FLAG_STEALTH)?(abs(ol->ob->x-op->x)<radius/2+1 &&
abs(ol->ob->y-op->y)<radius/2+1):
(abs(ol->ob->x-op->x)<radius&&
abs(ol->ob->y-op->y)<radius)))
{
CLEAR_FLAG(op,FLAG_SLEEP);
return 1;
}
return 0;
}
int move_randomly(object *op) {
int i;
for(i=0;i<15;i++);
if(move_object(op,RANDOM()%8+1))
return 1;
return 0;
}
/*
* Move-monster returns 1 if the object has been freed, otherwise 0.
*/
int move_monster(object *op) {
int dir,diff;
object *part, *owner, *enemy = find_enemy(op);
if(QUERY_FLAG(op, FLAG_SLEEP)||QUERY_FLAG(op, FLAG_BLIND)
#ifdef USE_LIGHTING
||((op->map->darkness>0)&&!QUERY_FLAG(op,FLAG_SEE_IN_DARK)
&&!QUERY_FLAG(op,FLAG_SEE_INVISIBLE))
#endif
) {
if(!check_wakeup(op,enemy))
return 0;
}
if(op->pick_up)
monster_check_pickup(op);
if(op->will_apply)
monster_apply_below(op); /* Check for items to apply below */
if(op->stats.Con&&op->stats.hp<op->stats.maxhp) {
op->stats.hp+=op->stats.Con;
if (QUERY_FLAG(op,FLAG_RUN_AWAY) &&
op->stats.hp >= (signed short)(((float)op->run_away/(float)100)*
(float)op->stats.maxhp))
CLEAR_FLAG(op, FLAG_RUN_AWAY);
if(op->stats.hp>op->stats.maxhp)
op->stats.hp=op->stats.maxhp;
}
if(QUERY_FLAG(op, FLAG_SCARED)&&!(RANDOM()%20))
CLEAR_FLAG(op,FLAG_SCARED); /* Time to regain some "guts"... */
if(!enemy) {
if(QUERY_FLAG(op, FLAG_ONLY_ATTACK)) {
remove_ob(op);
free_object(op);
return 1;
}
if (op->move_type & HI4) {
switch (op->move_type & HI4) {
case (PETMOVE):
pet_move (op);
if(QUERY_FLAG(op, FLAG_REMOVED)) {
remove_friendly_object(op);
free_object(op);
return 1;
}
break;
case (CIRCLE1):
circ1_move (op);
break;
case (CIRCLE2):
circ2_move (op);
break;
case (PACEV):
pace_movev(op);
break;
case (PACEH):
pace_moveh(op);
break;
case (PACEV2):
pace2_movev (op);
break;
case (PACEH2):
pace2_moveh (op);
break;
case (RANDO):
rand_move (op);
break;
case (RANDO2):
move_randomly (op);
break;
}
if(QUERY_FLAG(op, FLAG_FREED))
return 1;
return 0;
}
if (QUERY_FLAG(op,FLAG_RANDOM_MOVE))
(void) move_randomly(op);
return 0;
}
/* There is something to attack */
if((op->type&HI4) == PETMOVE && (owner = get_owner(op)) != NULL &&
op->map != owner->map)
{
follow_owner(op, owner);
if(QUERY_FLAG(op, FLAG_REMOVED) && FABS(op->speed) > 0.00001) {
remove_friendly_object(op);
free_object(op);
return 1;
}
return 0;
}
/* doppleganger code to change monster facing to that of the nearest
player */
if( (op->race != NULL)&& strcmp(op->race,"doppleganger") == 0){
op->face = enemy->face;
strcpy(op->name,enemy->name);
}
part = get_nearest_part(op,enemy);
dir=find_dir_2(part->x-enemy->x,part->y-enemy->y);
if(QUERY_FLAG(op,FLAG_IS_TURNING))
op->value=(enemy->x>op->x);
if(QUERY_FLAG(op, FLAG_SCARED) || QUERY_FLAG(op,FLAG_RUN_AWAY))
dir=absdir(dir+4);
if(QUERY_FLAG(op,FLAG_CONFUSED))
dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
if (!QUERY_FLAG(op, FLAG_SCARED)) {
if(QUERY_FLAG(op,FLAG_CAST_SPELL))
if(monster_cast_spell(op,part,enemy,dir))
return 0;
if(QUERY_FLAG(op,FLAG_READY_WAND)&&!(RANDOM()%3))
if(monster_use_wand(op,part,enemy,dir))
return 0;
if(QUERY_FLAG(op,FLAG_READY_ROD)&&!(RANDOM()%4))
if(monster_use_rod(op,part,enemy,dir))
return 0;
if(QUERY_FLAG(op,FLAG_READY_HORN)&&!(RANDOM()%5))
if(monster_use_horn(op,part,enemy,dir))
return 0;
#ifdef ALLOW_SKILLS
if(QUERY_FLAG(op,FLAG_READY_SKILL)&&!(RANDOM()%3))
if(monster_use_skill(op,part,enemy,dir))
return 0;
#endif
if(QUERY_FLAG(op,FLAG_READY_BOW)&&!(RANDOM()%2))
if(monster_use_bow(op,part,enemy,dir))
return 0;
}
if ((op->move_type & LO4) && !QUERY_FLAG(op, FLAG_SCARED)) {
switch (op->move_type & LO4) {
case DISTATT:
dir = dist_att (dir,op,enemy,part);
break;
case RUNATT:
dir = run_att (dir,op,enemy,part);
break;
case HITRUN:
dir = hitrun_att(dir,op,enemy);
break;
case WAITATT:
dir = wait_att (dir,op,enemy,part);
break;
case RUSH:
case ALLRUN:
break;
case DISTHIT:
dir = disthit_att (dir,op,enemy,part);
break;
case WAIT2:
dir = wait_att2 (dir,op,enemy,part);
break;
default:
LOG(llevDebug,"Illegal low mon-move: %d\n",op->move_type & LO4);
}
}
if (!dir)
return (0);
if (!QUERY_FLAG(op,FLAG_STAND_STILL))
{
if(move_object(op,dir)) /* Can the monster move directly toward player? */
return 0;
if(QUERY_FLAG(op, FLAG_SCARED) || !can_hit(part,enemy) || QUERY_FLAG(op,FLAG_RUN_AWAY))
{ /* Try move around corners if !close */
int maxdiff = (QUERY_FLAG(op, FLAG_ONLY_ATTACK) || RANDOM()&1) ? 1 : 2;
for(diff = 1; diff <= maxdiff; diff++)
{ /* try different detours */
int m = 1-(RANDOM()&2); /* Try left or right first? */
if(move_object(op,absdir(dir + diff*m)) ||
move_object(op,absdir(dir - diff*m)))
return 0;
}
}
}
/*
* Eneq(@csd.uu.se): Patch to make RUN_AWAY or SCARED monsters move a random
* direction if they can't move away.
*/
if (!QUERY_FLAG(op, FLAG_ONLY_ATTACK)&&(QUERY_FLAG(op,FLAG_RUN_AWAY)||QUERY_FLAG(op, FLAG_SCARED)))
if(move_randomly(op))
return 0;
/*
* Monster can't move...now see if it can hit the player...
* Eneq(@csd.uu.se): Added check to handle RUN_AWAY and berzerk attack from
* RUN_AWAY, locked in monster.
*/
if (!QUERY_FLAG(op, FLAG_FRIENDLY) && enemy == op->enemy) {
object *nearest_player = get_nearest_player(op);
if (nearest_player && nearest_player != enemy && !can_hit(part,enemy)) {
op->enemy = NULL;
enemy = nearest_player;
}
}
if(!QUERY_FLAG(op, FLAG_SCARED)&&can_hit(part,enemy))
{
if(QUERY_FLAG(op,FLAG_RUN_AWAY))
{
signed char tmp = (signed char)((float)part->stats.wc*(float)2);
part->stats.wc+=tmp;
#ifdef ALLOW_SKILLS
(void)skill_attack(enemy,part,0,NULL);
#else
(void)attack_ob(enemy,part);
#endif
part->stats.wc-=tmp;
} else
#ifdef ALLOW_SKILLS
(void)skill_attack(enemy,part,0,NULL);
#else
(void) attack_ob(enemy,part);
#endif
}
if(QUERY_FLAG(part,FLAG_FREED)) /* Might be freed by ghost-attack or hit-back */
return 1;
if(QUERY_FLAG(op, FLAG_ONLY_ATTACK)) {
remove_ob(op);
free_object(op);
return 1;
}
return 0;
}
int can_hit(object *ob1,object *ob2) {
if(QUERY_FLAG(ob1,FLAG_CONFUSED)&&!(RANDOM()%3))
return 0;
return abs(ob1->x-ob2->x)<2&&abs(ob1->y-ob2->y)<2;
}
/*Someday we may need this check */
int can_apply(object *who,object *item) {
return 1;
}
#define MAX_KNOWN_SPELLS 20
object *choose_random_spell(object *monster) {
object *altern[MAX_KNOWN_SPELLS];
object *tmp;
int i=0,j;
for(tmp=monster->inv;tmp!=NULL;tmp=tmp->below)
if(tmp->type==ABILITY||tmp->type==SPELLBOOK) {
if(tmp->stats.maxsp)
for(j=0;i<MAX_KNOWN_SPELLS&&j<tmp->stats.maxsp;j++)
altern[i++]=tmp;
else
altern[i++]=tmp;
if(i==MAX_KNOWN_SPELLS)
break;
}
if(!i)
return NULL;
return altern[RANDOM()%i];
}
int monster_cast_spell(object *head, object *part,object *pl,int dir) {
object *spell_item;
spell *sp;
int sp_typ, ability;
object *owner;
if(head->stats.sp<head->stats.maxsp) /* Generate spell-points */
head->stats.sp+=head->stats.Pow;
if(!(RANDOM()%3)) /* Don't want to cast spells so often */
return 0;
if(!(dir=path_to_player(part,pl,0)))
return 0;
if(QUERY_FLAG(head,FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
int dir2 = find_dir_2(head->x-owner->x, head->y-owner->y);
if(dirdiff(dir,dir2) < 2)
return 0; /* Might hit owner with spell */
}
if(QUERY_FLAG(head,FLAG_CONFUSED))
dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
if((spell_item=choose_random_spell(head))==NULL) {
LOG(llevMonster,"Turned off spells in %s\n",head->name);
CLEAR_FLAG(head, FLAG_CAST_SPELL); /* Will be turned on when picking up books */
return 0;
}
if(spell_item->stats.hp) {
/* Alternate long-range spell: check how far away enemy is */
if(isqrt(distance(part,pl))>6)
sp_typ=spell_item->stats.hp;
else
sp_typ=spell_item->stats.sp;
} else
sp_typ=spell_item->stats.sp;
if((sp=find_spell(sp_typ))==NULL) {
LOG(llevError,"Warning: Couldn't find spell in item.\n");
return 0;
}
if (sp->onself) /* Spell should be cast on caster (ie, heal, strength) */
dir = 0;
if(head->stats.sp<sp->sp) /* Monster doesn't have enough spell-points */
return 0;
head->stats.sp-=sp->sp;
ability = (spell_item->type==ABILITY && !(spell_item->attacktype&AT_MAGIC));
return cast_spell(part,part,dir,sp_typ,ability, spellNormal,NULL);
}
#ifdef ALLOW_SKILLS
/* monster_use_skill()-implemented 95-04-28 to allow monster skill use.
* Note that monsters do not need the skills SK_MELEE_WEAPON and
* SK_MISSILE_WEAPON to make those respective attacks, if we
* required that we would drastically increase the memory
* requirements of CF!!
*
* The skills we are treating here are all but those. -b.t.
*/
int monster_use_skill(object *head, object *part, object *pl,int dir) {
object *skill, *owner;
if(!(dir=path_to_player(part,pl,0)))
return 0;
if(QUERY_FLAG(head,FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
int dir2 = find_dir_2(head->x-owner->x, head->y-owner->y);
if(dirdiff(dir,dir2) < 1)
return 0; /* Might hit owner with skill -thrown rocks for example ?*/
}
if(QUERY_FLAG(head,FLAG_CONFUSED))
dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
/* skill selection - monster will use the next unused skill.
* well...the following scenario will allow the monster to
* toggle between 2 skills. One day it would be nice to make
* more skills available to monsters.
*/
for(skill=head->inv;skill!=NULL;skill=skill->below)
if(skill->type==SKILL && skill!=head->chosen_skill) {
head->chosen_skill=skill;
break;
}
if(!skill && !head->chosen_skill) {
LOG(llevDebug,"Error: Monster %s (%d) has FLAG_READY_SKILL without skill.\n",
head->name,head->count);
CLEAR_FLAG(head, FLAG_READY_SKILL);
return 0;
}
/* use skill */
return do_skill(head,dir,NULL);
}
#endif
/* For the future: Move this function together with case 3: in fire() */
int monster_use_wand(object *head,object *part,object *pl,int dir) {
object *wand, *owner;
if(!(dir=path_to_player(part,pl,0)))
return 0;
if(QUERY_FLAG(head,FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
int dir2 = find_dir_2(head->x-owner->x, head->y-owner->y);
if(dirdiff(dir,dir2) < 2)
return 0; /* Might hit owner with spell */
}
if(QUERY_FLAG(head,FLAG_CONFUSED))
dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
for(wand=head->inv;wand!=NULL;wand=wand->below)
if(wand->type==WAND&&QUERY_FLAG(wand,FLAG_APPLIED))
break;
if(wand==NULL) {
LOG(llevError,"Error: Monster %s (%d) HAS_READY_WAND() without wand.\n",
head->name,head->count);
CLEAR_FLAG(head, FLAG_READY_WAND);
return 0;
}
if(wand->stats.food<=0) {
apply(head,wand);
CLEAR_FLAG(head, FLAG_READY_WAND);
if (wand->arch) {
CLEAR_FLAG(wand, FLAG_ANIMATE);
wand->face = wand->arch->clone.face;
wand->speed = 0;
update_ob_speed(wand);
}
return 0;
}
if(cast_spell(part,wand,dir,wand->stats.sp,0,spellWand,NULL)) {
wand->stats.food--;
return 1;
}
return 0;
}
int monster_use_rod(object *head,object *part,object *pl,int dir) {
object *rod, *owner;
if(!(dir=path_to_player(part,pl,0)))
return 0;
if(QUERY_FLAG(head,FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
int dir2 = find_dir_2(head->x-owner->x, head->y-owner->y);
if(dirdiff(dir,dir2) < 2)
return 0; /* Might hit owner with spell */
}
if(QUERY_FLAG(head,FLAG_CONFUSED))
dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
for(rod=head->inv;rod!=NULL;rod=rod->below)
if(rod->type==ROD&&QUERY_FLAG(rod,FLAG_APPLIED))
break;
if(rod==NULL) {
LOG(llevError,"Error: Monster %s (%d) HAS_READY_ROD() without rod.\n",
head->name,head->count);
CLEAR_FLAG(head, FLAG_READY_ROD);
return 0;
}
if(rod->stats.hp<spells[rod->stats.sp].sp) {
return 0; /* Not recharged enough yet */
}
if(cast_spell(part,rod,dir,rod->stats.sp,0,spellRod,NULL)) {
drain_rod_charge(rod);
return 1;
}
return 0;
}
int monster_use_horn(object *head,object *part,object *pl,int dir) {
object *horn, *owner;
if(!(dir=path_to_player(part,pl,0)))
return 0;
if(QUERY_FLAG(head,FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
int dir2 = find_dir_2(head->x-owner->x, head->y-owner->y);
if(dirdiff(dir,dir2) < 2)
return 0; /* Might hit owner with spell */
}
if(QUERY_FLAG(head,FLAG_CONFUSED))
dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
for(horn=head->inv;horn!=NULL;horn=horn->below)
if(horn->type==ROD&&QUERY_FLAG(horn,FLAG_APPLIED))
break;
if(horn==NULL) {
LOG(llevError,"Error: Monster %s (%d) HAS_READY_HORN() without horn.\n",
head->name,head->count);
CLEAR_FLAG(head, FLAG_READY_HORN);
return 0;
}
if(horn->stats.hp<spells[horn->stats.sp].sp) {
return 0; /* Not recharged enough yet */
}
if(cast_spell(part,horn,dir,horn->stats.sp,0,spellHorn,NULL)) {
drain_rod_charge(horn);
return 1;
}
return 0;
}
int monster_use_bow(object *head, object *part, object *pl, int dir) {
object *bow, *arrow, *owner;
if(!(dir=path_to_player(part,pl,0)))
return 0;
if(QUERY_FLAG(head,FLAG_CONFUSED))
dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);
if(QUERY_FLAG(head,FLAG_FRIENDLY) && (owner = get_owner(head)) != NULL) {
int dir2 = find_dir_2(head->x-owner->x, head->y-owner->y);
if(dirdiff(dir,dir2) < 1)
return 0; /* Might hit owner with spell */
}
for(bow=head->inv;bow!=NULL;bow=bow->below)
if(bow->type==BOW&&QUERY_FLAG(bow,FLAG_APPLIED))
break;
if(bow==NULL) {
LOG(llevError,"Error: Monster %s (%d) HAS_READY_BOW() without bow.\n",
head->name,head->count);
CLEAR_FLAG(head, FLAG_READY_BOW);
return 0;
}
if((arrow=find_arrow(head,bow->race)) == NULL) {
/* Out of arrows */
apply(head,bow);
CLEAR_FLAG(head, FLAG_READY_BOW);
return 0;
}
arrow=get_split_ob(arrow,1);
set_owner(arrow,head);
arrow->direction=dir;
arrow->x=part->x,arrow->y=part->y;
arrow->speed = 1;
update_ob_speed(arrow);
arrow->speed_left=0;
arrow->face=&new_faces[arrow->arch->faces[dir]];
arrow->stats.sp = arrow->stats.wc; /* save original wc and dam */
arrow->stats.hp = arrow->stats.dam;
arrow->stats.dam+= (QUERY_FLAG(bow, FLAG_NO_STRENGTH) ? 0 : head->level)
+bow->stats.dam+bow->magic+arrow->magic;
arrow->stats.wc= head->stats.wc - bow->magic - arrow->magic -
arrow->stats.wc;
arrow->map=head->map;
SET_FLAG(arrow, FLAG_FLYING);
SET_FLAG(arrow, FLAG_FLY_ON);
SET_FLAG(arrow, FLAG_WALK_ON);
insert_ob_in_map(arrow,head->map);
move_arrow(arrow);
return 1;
}
int check_good_weapon(object *who, object *item) {
object *other_weap;
int prev_dam=who->stats.dam;
for(other_weap=who->inv;other_weap!=NULL;other_weap=other_weap->below)
if(other_weap->type==item->type&&QUERY_FLAG(other_weap,FLAG_APPLIED))
break;
if(other_weap==NULL) /* No other weapons */
return 1;
if (!apply(who,item)) {
LOG(llevMonster,"Can't wield %s(%d).\n",item->name,item->count);
return 0;
}
if(who->stats.dam < prev_dam && !QUERY_FLAG(other_weap,FLAG_FREED)) {
/* New weapon was worse. (Note ^: Could have been freed by merging) */
if (!apply(who,other_weap))
LOG(llevMonster,"Can't rewield %s(%d).\n",item->name,item->count);
return 0;
}
return 1;
}
int check_good_armour(object *who, object *item) {
object *other_armour;
int prev_ac = who->stats.ac;
for (other_armour = who->inv; other_armour != NULL;
other_armour = other_armour->below)
if (other_armour->type == item->type && QUERY_FLAG(other_armour,FLAG_APPLIED))
break;
if (other_armour == NULL) /* No other armour, use the new */
return 1;
if (!apply(who, item)) {
LOG(llevMonster, "Can't take off %s(%d).\n",item->name,item->count);
return 0;
}
if(who->stats.ac < prev_ac && !QUERY_FLAG(other_armour,FLAG_FREED)) {
/* New armour was worse. *Note ^: Could have been freed by merging) */
if (!apply(who, other_armour))
LOG(llevMonster,"Can't rewear %s(%d).\n", item->name, item->count);
return 0;
}
return 1;
}
/*
* monster_check_pickup(): checks for items that monster can pick up.
*
* Vick's (vick@bern.docs.uu.se) fix 921030 for the sweeper blob.
* Each time the blob passes over some treasure, it will
* grab it a.s.a.p.
*
* Eneq(@csd.uu.se): This can now be defined in the archetypes, added code
* to handle this.
*/
void monster_check_pickup(object *monster) {
object *tmp,*next;
#if 0
object *outofdate;
#endif
for(tmp=monster->below;tmp!=NULL;tmp=next) {
next=tmp->below;
if (monster_can_pick(monster,tmp)) {
remove_ob(tmp);
tmp = insert_ob_in_ob(tmp,monster);
(void) monster_check_apply(monster,tmp);
}
}
}
/*
* monster_can_pick(): If the monster is interested in picking up
* the item, then return 0. Otherwise 0.
* Instead of pick_up, flags for "greed", etc, should be used.
* I've already utilized flags for bows, wands, rings, etc, etc. -Frank.
*/
int monster_can_pick(object *monster, object *item) {
int flag=0;
if(!can_pick(monster,item))
return 0;
if(QUERY_FLAG(item,FLAG_UNPAID))
return 0;
if (monster->pick_up&64) /* All */
flag=1;
else switch(item->type) {
case MONEY:
case GEM:
flag=monster->pick_up&2;
break;
case FOOD:
flag=monster->pick_up&4;
break;
case WEAPON:
flag=(monster->pick_up&8)||QUERY_FLAG(monster,FLAG_USE_WEAPON);
break;
case ARMOUR:
case SHIELD:
case HELMET:
flag=(monster->pick_up&16)||QUERY_FLAG(monster,FLAG_USE_ARMOUR);
break;
case RING:
flag=QUERY_FLAG(monster,FLAG_USE_RING);
break;
case WAND:
flag=QUERY_FLAG(monster,FLAG_USE_WAND);
break;
case SPELLBOOK:
flag=(monster->arch!=NULL&&QUERY_FLAG((&monster->arch->clone),FLAG_CAST_SPELL));
break;
case BOW:
case ARROW:
flag=QUERY_FLAG(monster,FLAG_USE_BOW);
break;
}
if (((!(monster->pick_up&32))&&flag) || ((monster->pick_up&32)&&(!flag)))
return 1;
return 0;
}
/*
* monster_apply_below():
* Vick's (vick@bern.docs.uu.se) @921107 -> If a monster who's
* eager to apply things, encounters something apply-able,
* then make him apply it
*/
void monster_apply_below(object *monster) {
object *tmp, *next;
for(tmp=monster->below;tmp!=NULL;tmp=next) {
next=tmp->below;
switch (tmp->type) {
case HANDLE:
if (monster->will_apply&1)
apply(monster,tmp);
break;
case TREASURE:
if (monster->will_apply&2)
apply(monster,tmp);
break;
case SCROLL: /* Ideally, they should wait until they meet a player */
if (QUERY_FLAG(monster,FLAG_USE_SCROLL))
apply(monster,tmp);
break;
}
}
}
/*
* monster_check_apply() is meant to be called after an item is
* inserted in a monster.
* If an item becomes outdated (monster found a better item),
* a pointer to that object is returned, so it can be dropped.
* (so that other monsters can pick it up and use it)
*/
void monster_check_apply(object *mon, object *item) {
if(item->type==SPELLBOOK&&
mon->arch!=NULL&&(QUERY_FLAG((&mon->arch->clone),FLAG_CAST_SPELL))) {
SET_FLAG(mon, FLAG_CAST_SPELL);
return;
}
if(QUERY_FLAG(mon,FLAG_USE_BOW) && item->type==ARROW)
{ /* Check for the right kind of bow */
object *bow;
for(bow=mon->inv;bow!=NULL;bow=bow->below)
if(bow->type==BOW && bow->race==item->race) {
SET_FLAG(mon, FLAG_READY_BOW);
LOG(llevMonster,"Found correct bow for arrows.\n");
if(!QUERY_FLAG(bow, FLAG_APPLIED))
apply(mon,bow);
break;
}
}
/* Mol: (mol@meryl.csd.uu.se) If can_apply <number> is defined in the objects
archetype it can apply the object. See global.h for more info. */
if (can_apply(mon,item)) {
int flag=0;
if (mon->can_apply&64) /* All */
flag=1;
else switch(item->type) {
case TREASURE:
flag=0;
break;
case POTION:
flag=mon->can_apply&2;
break;
case FOOD: /* Can a monster eat food ? Yes! (it heals) */
flag=mon->can_apply&4;
break;
case WEAPON:
/*
* Apply only if it's a better weapon than the used one.
* All "standard" monsters need to adjust their wc to use the can_apply on
* weapons.
*/
flag=((mon->can_apply&8)||QUERY_FLAG(mon,FLAG_USE_WEAPON))&&
check_good_weapon(mon,item);
break;
case ARMOUR:
case HELMET:
case SHIELD:
flag=((mon->can_apply&16)||QUERY_FLAG(mon,FLAG_USE_ARMOUR))&&
check_good_armour(mon,item);
break;
case RING:
flag=QUERY_FLAG(mon,FLAG_USE_RING);
break;
case WAND:
flag=QUERY_FLAG(mon,FLAG_USE_WAND);
break;
case BOW:
flag=QUERY_FLAG(mon,FLAG_USE_BOW);
}
if (((!(mon->can_apply&32))&&flag) ||((mon->can_apply&32)&&(!flag))) {
/* &32 reverses behaviour. See global.h */
if(!QUERY_FLAG(item,FLAG_APPLIED))
apply(mon,item);
if (item->type==BOW&&present_in_ob(item->stats.maxsp,mon)!=NULL)
SET_FLAG(mon, FLAG_READY_BOW);
}
return;
#if 0
if(!QUERY_FLAG(item,FLAG_APPLIED))
return item;
{
object *tmp;
for(tmp=mon->inv;tmp!=NULL;tmp=tmp->below)
if(tmp!=item&&tmp->type==item->type)
return tmp;
}
#endif
}
return;
}
void npc_call_help(object *op) {
int x,y;
object *npc;
for(x = -3; x < 4; x++)
for(y = -3; y < 4; y++) {
if(out_of_map(op->map,op->x+x,op->y+y))
continue;
for(npc = get_map_ob(op->map,op->x+x,op->y+y);npc!=NULL;npc=npc->above)
if(QUERY_FLAG(npc, FLAG_ALIVE)&&QUERY_FLAG(npc, FLAG_UNAGGRESSIVE))
npc->enemy = op->enemy;
}
}
int dist_att (int dir , object *ob, object *enemy, object *part) {
int dist;
if (can_hit(part,enemy))
return dir;
dist = distance (ob,enemy);
if (dist < 10)
return absdir(dir+4);
else if (dist>81) {
return dir;
}
return 0;
}
int run_att (int dir, object *ob, object *enemy,object *part) {
if ((can_hit (part,enemy) && ob->move_status <20) || ob->move_status <20)
{
ob->move_status++;
return (dir);
}
else if (ob->move_status >20)
ob->move_status = 0;
return absdir (dir+4);
}
int hitrun_att (int dir, object *ob,object *enemy) {
if (ob->move_status ++ < 25)
return dir;
else if (ob->move_status <50)
return absdir (dir+4);
else
ob->move_status = 0;
return absdir(dir+4);
}
int wait_att (int dir, object *ob,object *enemy,object *part) {
int inrange = can_hit (part, enemy);
if (ob->move_status || inrange)
ob->move_status++;
if (ob->move_status == 0)
return 0;
else if (ob->move_status <10)
return dir;
else if (ob->move_status <15)
return absdir(dir+4);
ob->move_status = 0;
return 0;
}
int disthit_att (int dir, object *ob, object *enemy, object *part) {
if (ob->stats.hp < (signed short)(((float)ob->run_away/(float)50*
(float)ob->stats.maxhp)))
return dir;
return dist_att (dir,ob,enemy,part);
}
int wait_att2 (int dir, object *ob,object *enemy,object *part) {
int dist = distance (ob,enemy);
if ( dist < 9)
return absdir (dir+4);
return 0;
}
void circ1_move (object *ob) {
static int circle [12] = {3,3,4,5,5,6,7,7,8,1,1,2};
if(++ob->move_status > 11)
ob->move_status = 0;
if (!(move_object(ob,circle[ob->move_status])))
(void) move_object(ob,RANDOM()%8+1);
}
void circ2_move (object *ob) {
static int circle[20] = {3,3,3,4,4,5,5,5,6,6,7,7,7,8,8,1,1,1,2,2};
if(++ob->move_status > 19)
ob->move_status = 0;
if(!(move_object(ob,circle[ob->move_status])))
(void) move_object(ob,RANDOM()%8+1);
}
void pace_movev(object *ob) {
if (ob->move_status++ > 6)
ob->move_status = 0;
if (ob->move_status < 4)
(void) move_object (ob,5);
else
(void) move_object(ob,1);
}
void pace_moveh (object *ob) {
if (ob->move_status++ > 6)
ob->move_status = 0;
if (ob->move_status < 4)
(void) move_object(ob,3);
else
(void) move_object(ob,7);
}
void pace2_movev (object *ob) {
if (ob->move_status ++ > 16)
ob->move_status = 0;
if (ob->move_status <6)
(void) move_object (ob,5);
else if (ob->move_status < 8)
return;
else if (ob->move_status <13)
(void) move_object (ob,1);
else return;
}
void pace2_moveh (object *ob) {
if (ob->move_status ++ > 16)
ob->move_status = 0;
if (ob->move_status <6)
(void) move_object (ob,3);
else if (ob->move_status < 8)
return;
else if (ob->move_status <13)
(void) move_object (ob,7);
else return;
}
void rand_move (object *ob) {
int i;
if (ob->move_status <1 || ob->move_status >8 ||
!(move_object(ob,ob->move_status|| ! (RANDOM()% 9))))
for (i = 0; i < 5; i++)
if (move_object(ob,ob->move_status = RANDOM()%8+1))
return;
}
void check_earthwalls(object *op, int x, int y) {
object *tmp;
tmp = get_map_ob(op->map, x, y);
if (tmp!= NULL)
while(tmp->above != NULL)
tmp=tmp->above;
if (tmp!= NULL && tmp->type == EARTHWALL)
hit_player(tmp,op->stats.dam,op,AT_PHYSICAL);
}
void check_doors(object *op, int x, int y) {
object *tmp;
tmp = get_map_ob(op->map, x, y);
if (tmp!= NULL)
while(tmp->above != NULL)
tmp=tmp->above;
if (tmp!= NULL && tmp->type == DOOR)
hit_player(tmp,1000,op,AT_PHYSICAL);
}
/*
* move_object() tries to move object op in the direction "dir".
* If it fails (something blocks the passage), it returns 0,
* otherwise 1.
* This is an improvement from the previous move_ob(), which
* removed and inserted objects even if they were unable to move.
*/
int move_object(object *op, int dir) {
int newx = op->x+freearr_x[dir];
int newy = op->y+freearr_y[dir];
object *tmp;
if(blocked_link(op, newx, newy)) /* Not all features from blocked_two yet */
return 0; /* (Not efficient enough yet) */
if(op->more != NULL && !move_object(op->more, dir))
return 0;
if(op->will_apply&4)
check_earthwalls(op,newx,newy);
if(op->will_apply&8)
check_doors(op,newx,newy);
if(op->head)
return 1;
remove_ob(op);
for(tmp = op; tmp != NULL; tmp = tmp->more)
tmp->x+=freearr_x[dir], tmp->y+=freearr_y[dir];
insert_ob_in_map(op, op->map);
return 1;
}
msglang *parse_message(char *msg) {
msglang *msgs;
int nrofmsgs, msgnr, i;
char *cp, *line, *last;
char *buf = strdup_local(msg);
/* First find out how many messages there are. A @ for each. */
for (nrofmsgs = 0, cp = buf; *cp; cp++)
if (*cp == '@')
nrofmsgs++;
if (!nrofmsgs)
return NULL;
msgs = (msglang *) malloc(sizeof(msglang));
msgs->messages = (char **) malloc(sizeof(char *) * (nrofmsgs + 1));
msgs->keywords = (char ***) malloc(sizeof(char **) * (nrofmsgs + 1));
for(i=0; i<=nrofmsgs; i++) {
msgs->messages[i] = NULL;
msgs->keywords[i] = NULL;
}
for (last = NULL, cp = buf, msgnr = 0;*cp; cp++)
if (*cp == '@') {
int nrofkeywords, keywordnr;
*cp = '\0'; cp++;
if(last != NULL)
msgs->messages[msgnr++] = strdup_local(last);
if(strncmp(cp,"match",5)) {
LOG(llevError,"Unsupported command in message.\n");
free(buf);
return NULL;
}
for(line = cp + 6, nrofkeywords = 0; *line != '\n' && *line; line++)
if(*line == '|')
nrofkeywords++;
if(line > cp + 6)
nrofkeywords++;
if(nrofkeywords < 1) {
LOG(llevError,"Too few keywords in message.\n");
free(buf);
free_messages(msgs);
return NULL;
}
msgs->keywords[msgnr] = (char **) malloc(sizeof(char **) * (nrofkeywords +1));
msgs->keywords[msgnr][nrofkeywords] = NULL;
last = cp + 6;
cp = strchr(cp,'\n');
if(cp != NULL)
cp++;
for(line = last, keywordnr = 0;line<cp && *line;line++)
if(*line == '\n' || *line == '|') {
*line = '\0';
if (last != line)
msgs->keywords[msgnr][keywordnr++] = strdup_local(last);
last = line + 1;
}
last = cp;
}
if(last != NULL)
msgs->messages[msgnr++] = strdup_local(last);
free(buf);
return msgs;
}
void free_messages(msglang *msgs) {
int messages, keywords;
for(messages = 0; msgs->messages[messages]; messages++) {
if(msgs->keywords[messages])
for(keywords = 0; msgs->keywords[messages][keywords]; keywords++)
free(msgs->keywords[messages][keywords]);
free(msgs->messages[messages]);
}
free(msgs->messages);
free(msgs->keywords);
}
void dump_messages(msglang *msgs) {
int messages, keywords;
for(messages = 0; msgs->messages[messages]; messages++) {
LOG(llevDebug, "@match ");
for(keywords = 0; msgs->keywords[messages][keywords]; keywords++)
LOG(llevDebug, "%s ",msgs->keywords[messages][keywords]);
LOG(llevDebug, "\n%s\n",msgs->messages[messages]);
}
}
void communicate(object *op, char *txt) {
object *npc;
int i;
for(i = 0; i < 24; i++)
if (!out_of_map(op->map, op->x+freearr_x[i], op->y+freearr_y[i]))
for(npc = get_map_ob(op->map,op->x+freearr_x[i],op->y+freearr_y[i]);
npc != NULL; npc = npc->above)
if (npc->type == MAGIC_EAR)
(void) talk_to_wall(npc, txt); /* Maybe exit after 1. success? */
else
if (talk_to_npc(npc,txt))
return; /* Can be crowded */
}
int talk_to_npc(object *npc, char *txt) {
msglang *msgs;
int i,j;
if(npc->msg == NULL || *npc->msg != '@')
return 0;
if((msgs = parse_message(npc->msg)) == NULL)
return 0;
#if 0 /* Turn this on again when enhancing parse_message() */
if(debug)
dump_messages(msgs);
#endif
for(i=0; msgs->messages[i]; i++)
for(j=0; msgs->keywords[i][j]; j++)
if(msgs->keywords[i][j][0] == '*' || re_cmp(txt,msgs->keywords[i][j])) {
char buf[MAX_BUF];
sprintf(buf,"The %s says:",query_name(npc));
new_info_map(NDI_NAVY|NDI_UNIQUE, npc->map,buf);
new_info_map(NDI_NAVY | NDI_UNIQUE, npc->map, msgs->messages[i]);
free_messages(msgs);
return 1;
}
free_messages(msgs);
return 0;
}
int talk_to_wall(object *npc, char *txt) {
msglang *msgs;
int i,j;
if(npc->msg == NULL || *npc->msg != '@')
return 0;
if((msgs = parse_message(npc->msg)) == NULL)
return 0;
if(debug)
dump_messages(msgs);
for(i=0; msgs->messages[i]; i++)
for(j=0; msgs->keywords[i][j]; j++)
if(msgs->keywords[i][j][0] == '*' || re_cmp(txt,msgs->keywords[i][j])) {
if (msgs->messages[i] && *msgs->messages[i] != 0)
new_info_map(NDI_NAVY | NDI_UNIQUE, npc->map,msgs->messages[i]);
free_messages(msgs);
use_trigger(npc);
return 1;
}
free_messages(msgs);
return 0;
}